package logger

import (
	"fmt"
	"sync"
	"time"
)

type LogLevel int

const LevelColorSetClear = "\033[0m"

const (
	// different colors log text:
	_            = iota + 30 // black
	ColorRed                 // red
	ColorGreen               // green
	ColorYellow              // yellow
	ColorBlue                // blue
	ColorMagenta             // magenta
	_                        // cyan
	ColorWhite               // white
)

// LogType
const (
	TextType = "text"
	JsonType = "json"
)

// MessageLevel
const (
	NOTSET   = iota
	DEBUG    = LogLevel(10 * iota) // DEBUG = 10
	INFO     = LogLevel(10 * iota) // INFO = 20
	NOTICE   = LogLevel(10 * iota) // INFO = 30
	WARNING  = LogLevel(10 * iota) // WARNING = 40
	ERROR    = LogLevel(10 * iota) // ERROR = 50
	CRITICAL = LogLevel(10 * iota) // CRITICAL = 60
)

// LevelColorFlag, MessageLevel color flag.
var LevelColorFlag = []string{
	DEBUG:    colorSet(ColorWhite, 0),
	INFO:     colorSet(ColorGreen, 0),
	NOTICE:   colorSet(ColorBlue, 0),
	WARNING:  colorSet(ColorYellow, 0),
	ERROR:    colorSet(ColorRed, 1),
	CRITICAL: colorSet(ColorMagenta, 1),
}

var LevelString = map[LogLevel]string{
	DEBUG:    "DEBUG",
	INFO:     "INFO",
	NOTICE:   "NOTICE",
	WARNING:  "WARNING",
	ERROR:    "ERROR",
	CRITICAL: "CRITICAL",
}

func colorSet(l LogLevel, way int) string {
	return fmt.Sprintf("\033[%d;%dm", way, LogLevel(l))
}

// Logger main logger struct
type Logger struct {
	Level         LogLevel
	Record        *Record
	Format        *Message
	StreamHandler *StreamMessageHandler
	SyncInterval  int
}

// LoggerFiles
type loggerFileMap struct {
	mutex     sync.Mutex
	LoggerMap map[string]*Logger
}

const MaxWarnLogFileCount = 20

var loggerFiles loggerFileMap

func (log *loggerFileMap) newLogFile(fileName string, loggerHandler *Logger) *Logger {
	if loggerHandler == nil {
		if handler, ok := log.LoggerMap[fileName]; ok {
			return handler
		}
		return nil
	}

	log.mutex.Lock()
	defer log.mutex.Unlock()

	if log.LoggerMap == nil || len(log.LoggerMap) == 0 {
		log.LoggerMap = make(map[string]*Logger, 0)
	}

	log.LoggerMap[fileName] = loggerHandler

	if len(log.LoggerMap) >= MaxWarnLogFileCount {
		loggerHandler.Warning("The log files is too more, it will reduced system performance")
	}
	return loggerHandler
}

// New get logger struct
func New(level LogLevel, logType string, logDir string, syncInterval int) *Logger {
	if logDir != "" && logDir[len(logDir)-1:] == "/" {
		logDir = logDir[:len(logDir)-1]
	}
	logFileName := "default"
	stream := &StreamMessageHandler{
		LogFileName: logFileName,
		LogExt:      ".log",
	}

	if logDir != "" {
		stream.LogDir = logDir
	}

	if syncInterval > 0 {
		go func() {
			for {
				stream.SyncLog()
				time.Sleep(time.Duration(syncInterval) * time.Millisecond)
			}
		}()
	}

	loggerHandler := &Logger{
		Level: level,
		Format: &Message{
			FormatType: logType,
		},
		StreamHandler: stream,
		SyncInterval:  syncInterval,
	}

	loggerFiles.newLogFile(logFileName, loggerHandler)
	return loggerHandler
}

// log, sed message to handler.
func (l *Logger) log(level LogLevel, format string, a ...interface{}) {
	if level >= l.Level {
		l.Record = GetMessageRecord(level, format, a...)

		if l.StreamHandler != nil {
			l.StreamHandler.Write([]byte(l.Format.GetMessage(l)))
			if l.SyncInterval == 0 {
				l.StreamHandler.SyncLog()
			}
		}
	}
}

// LogFile, change default log file name.
func (l *Logger) LogFile(fileName string) *Logger {
	logFileItem := loggerFiles.newLogFile(fileName, nil)
	if logFileItem != nil {
		return logFileItem
	}

	stream := &StreamMessageHandler{
		LogFileName: fileName,
		LogExt:      l.StreamHandler.LogExt,
		LogDir:      l.StreamHandler.LogDir,
	}

	newHandler := &Logger{
		Level:         l.Level,
		Format:        l.Format,
		StreamHandler: stream,
		SyncInterval:  l.SyncInterval,
	}
	if l.SyncInterval > 0 {
		go func() {
			for {
				stream.SyncLog()
				time.Sleep(time.Duration(l.SyncInterval) * time.Millisecond)
			}
		}()
	}

	loggerFiles.newLogFile(fileName, newHandler)

	return newHandler
}

// Debug, record DEBUG message.
func (l *Logger) Debug(format string, a ...interface{}) {
	l.log(DEBUG, format, a...)
}

// Info, record INFO message.
func (l *Logger) Info(format string, a ...interface{}) {
	l.log(INFO, format, a...)
}

// Notice, record INFO message.
func (l *Logger) Notice(format string, a ...interface{}) {
	l.log(NOTICE, format, a...)
}

// Warning, record WARNING message.
func (l *Logger) Warning(format string, a ...interface{}) {
	l.log(WARNING, format, a...)
}

// Error, record ERROR message.
func (l *Logger) Error(format string, a ...interface{}) {
	l.log(ERROR, format, a...)
}

// Critical, record CRITICAL message.
func (l *Logger) Critical(format string, a ...interface{}) {
	l.log(CRITICAL, format, a...)
}
